#include <math.h>
#include <string.h>

#include "base/Configuration.h"
#include "base/EvictRequest.h"
#include "base/LoadRequest.h"
#include "base/Response.h"
#include "base/Simulator.h"
#include "base/SnoopACK.h"
#include "base/SnoopRequest.h"
#include "base/SnoopResponse.h"
#include "base/StoreRequest.h"
#include "scc/SCCSimulator.h"
#include "scc/MESICache.h"
#include "mem/IsDirtyRequest.h"
#include "mem/SnoopDelayRequest.h"
#include "mem/RawMemoryRequest.h"
#include "base/simapi.h"
#include <map>


NEW_DELETE_OPERATORS_CPP(MESICache,100);

#ifdef CS315A_DEBUG
#define dprintf(...)  printf(__VA_ARGS__)
#else
#define dprintf(...) //fprintf(stderr, __VA_ARGS__)
#endif // DEBUG

unsigned int VAtoInt (VA addr) {
    return ADDR_UINT32(addr);
}

MESICache::MESICache (const char* _name,
                      uint _size,
                      uint _block_size,
                      uint _assoc,
                      uint _vcsize,
                      uint _delay,
                      uint _mshrs,
                      uint _snoopDelay,
                      LineFlags* _protoFlags,
                      Counter* _statAccesses,
                      Counter* _statHits,
                      Counter* _statMisses,
                      Bucket* _stallBkt,
                      Bucket* _unavailBkt)
:
    Cache::Cache(_name, _size, _block_size, _assoc, _vcsize, _delay, _mshrs,
                 _protoFlags, _statAccesses, _statHits, _statMisses, _stallBkt,
                 _unavailBkt),
    snoopDelay(_snoopDelay),
    snoopRequests(NEW List<PacketDelay*>)
{
    registerPacketHandler(typeid(SnoopRequest),
                          (PacketHandlerPtr)&MESICache::handle_SnoopDelay);
    registerPacketHandler(typeid(RawMemoryRequest),
                         (PacketHandlerPtr)&MESICache::handle_RawMemoryRequest);

    ignorePacketType(typeid(IsDirtyRequest));

    llsc = NULL;
    Ocounter = 1;
}

MESICache::~MESICache () {
    FILE* fout;
    fout = fopen("Ocounter.txt", "a");
    fprintf(fout, "%lf\n", Ocounter);
    fclose(fout);

    if (global_sim->cfg.shutdown_asserts) {
        if (snoopRequests->getLength() != 0) {
            // there can be pending evicton requests.
            // there shouldn't be load request and line flush request.
            while (!snoopRequests->empty()) {
                PacketDelay* pd = snoopRequests->remove();
                CAST_ASSERT(SnoopRequest*, pd->request);
		ASSERT(pd->request != NULL);
                delete pd->request;
                FREE(pd);
            }
        }
   }

    ASSERT(snoopRequests != NULL);
   delete snoopRequests;
}

void MESICache::tick () {
    // first, handle the delay for the incoming messages
    // DataRequest
    // pass the tick to Cache class
    Cache::tick();

    // we process 1 snoop request per cycle.
    if (!snoopRequests->empty() &&
        global_sim->getSimclock() >= snoopRequests->peek()->alarm)
    {
        PacketDelay* dr = snoopRequests->remove();
        SnoopRequest* r = (SnoopRequest*)dr->request;
        FREE(dr);

        handle_SnoopRequest(r);
    }

}

bool MESICache::stalled (Packet* r) {
    if (stallBySnoop > 0) {
        if (global_sim->getSimclock() - lastAccessClock <= stallBySnoop) {
            return true;
        } else {
            stallBySnoop = 0;
        }
    }

    // this is to prevent the race between the snoop request in the snoop queue
    // and the data request coming in.
    if (r != NULL && dynamic_cast<DataRequest*>(r)) {
        DataRequest* dr = CAST_OR_DIE(DataRequest*, r);
        if (isTherePendingSnoopRequest(dr->getAddress())) {
            cacheLog->d("%s:%lli:%0llx %s STALLED BY PENDING SNOOP REQUEST :\n",
                        dr->getSource()->getName(),
                        global_sim->getSimclock(),
                        dr->getAddress(),
                        name);          
            return true;
        }
    }
    
    return Cache::stalled(r);
}

Result MESICache::handleAccess (DataRequest* dr, List<CacheEntry*>* list) {

  VA addr = blockAddr(dr->getAddress());
  uint pid = (dr->getProcessor())->getID();
  ASSERT(pid+1);
  ASSERT(addr+1);
  dprintf("handleAccess[%d]: Start of function with addr=%0llx\n",(int)pid, dr->getAddress());
  if (isStoreConditional(dr)) {
    cacheLog->d(" ATOMIC ");
    dprintf("handleAccess[%d]: SC in %0llx\n", (int)pid, addr);
    return handleStoreConditional (dr, list);
  } else if (isLoadLock(dr)) {
    cacheLog->d(" ATOMIC\n");
    llsc = addr;
    dprintf("handleAccess[%d]: LL in %0llx\n", (int)pid, llsc);
  }

  if (list->empty()) {
    // all entries that are not marked otherwise in the cache are I
    //      dprintf ("Miss in %0llx\n", dr->getAddress());

    dprintf("handleAccess[%d]: Miss on addr=%0llx\n",(int)pid, dr->getAddress());

    if (canMiss(dr->getAddress())) {
      // miss, locate a new victim
      return handleMiss(dr, assignVictimLine(dr));
    } 
          
    cacheLog->d("%s:%lli:%0llx %s STALL ON MISS\n",
		dr->getSource()->getName(),
		global_sim->getSimclock(),
		dr->getAddress(),
		getName());
    return stall;
  }

    // it was a hit...
    //    dprintf ("Hit in %0llx\n", dr->getAddress());
  dprintf("handleAccess[%d]: Hit on addr=%0llx\n",(int)pid, dr->getAddress());

    ASSERT(list->size() == 1);
    CacheEntry* entry = list->peek();
    MESIFlags* entry_flags = CAST_OR_DIE(MESIFlags*, entry->flags);
    ASSERT(entry_flags->isValid());

    // cs315a:
    // This is a cache hit. entry contains the cache entry that the access
    // hit on, entry_flags its flags.  Your job is to handle this access.

    if (IS_A(StoreRequest*, dr)) {
        StoreRequest* sr = (StoreRequest*)dr;
        // handle a store hit...
	MESIFlags * mf= entry_flags;
	// if shared
	if (mf->is_S())
	  {
	    //	    dprintf("handleAccess:MESI flag is shared!...\n");
	    // we mark this as invalid and treat it as a miss
	    dprintf("handleAccess[%d]: addr=%0llx is SHARED and this is a STORE ... invalidating the line and treating as MISS\n",(int)pid, dr->getAddress());

	    mf->setValid(false);
	    ASSERT(entry != NULL);
	    hash->remove(entry);
	    delete entry;
	    // all entries that are not marked otherwise in the cache are I
	    if (canMiss(dr->getAddress())) {
	      // miss, locate a new victim
	      return handleMiss(dr, assignVictimLine(dr));
	    } 
	    
	    cacheLog->d("%s:%lli:%0llx %s STALL ON MISS\n",
			dr->getSource()->getName(),
			global_sim->getSimclock(),
			dr->getAddress(),
			getName());
	    return stall;
	  }
	else if(mf->is_E())
	  {
	    //	    dprintf("handleAccess:MESI flag is exclusive!...\n");
	    dprintf("handleAccess[%d]: addr=%0llx is EXCLUSIVE and this is a STORE ... -->MODIFIED\n",(int)pid, dr->getAddress());
	    mf->setDirty(true);
	  }
	else if (mf->is_M())
	  {
	    //	    dprintf("handleAccess:MESI flag is modified!...\n");
	    dprintf("handleAccess[%d]: addr=%0llx is MODIFIED and this is a STORE\n",(int)pid, dr->getAddress());
	  }
	else{
	  Sim_Print0 ("ERROR in handleAccess: flags imply I state on a hit\n");
	  ASSERT(0);
	}

        return handleStoreHit(sr, entry);
    } else if (IS_A(LoadRequest*, dr)) {
        LoadRequest* lr = (LoadRequest*)dr;
        // handle a load hit...
	dprintf("handleAccess[%d]: addr=%0llx this is a LOAD\n",(int)pid, dr->getAddress());
        return handleLoadHit(lr, entry);
    } 
    
    ASSERT(false);
    return success;
}

bool MESICache::isStoreConditional (DataRequest* dr) {
    return (IS_A(StoreRequest*, dr) && 
            isL1() &&
            ((StoreRequest*)dr)->isExclusive());
}

// cs315a: This can be used to determine if a load is load locked
bool MESICache::isLoadLock (DataRequest* dr) {
    return (IS_A(LoadRequest*, dr) && 
            isL1() &&
            ((LoadRequest*)dr)->isExclusive());
}

Result MESICache::handleStoreConditional (DataRequest* dr,  List<CacheEntry*>* list)
{
  
  VA addr = blockAddr(dr->getAddress());
  uint pid = (dr->getProcessor())->getID();
  ASSERT(pid+1);
  ASSERT(addr+1);
  dprintf("handleStoreConditional[%d]: addr=%0llx\n", (int)pid, addr);
	
  // 1. miss but there is a corresponding LL in the register:
  //    -> Handle the miss regularly
  if (list->empty() && (addr == llsc)) {
    // all entries that are not marked otherwise in the cache are I

    dprintf ("handleStoreConditional: miss on %0llx\n", addr);

    if (canMiss(dr->getAddress())) {
      // miss, locate a new victim
      return handleMiss(dr, assignVictimLine(dr));
    } 
          
    cacheLog->d("%s:%lli:%0llx %s STALL ON MISS (SC)\n",
		dr->getSource()->getName(),
		global_sim->getSimclock(),
		dr->getAddress(),
		getName());
    return stall;
  }
  
  // 2. hit in cache and hit in LLSC register
  else if ((list->size() == 1) && (addr == llsc)) {
    CacheEntry* entry = list->peek();
    MESIFlags* entry_flags = CAST_OR_DIE(MESIFlags*, entry->flags);
    ASSERT(entry); // these asserts just use the variables to eliminate
    ASSERT(entry_flags); // unused variable warning
    // cs315a: 
    // store conditional hit in cache, if it should succeed, you
    // should handle it here (as if it were a normal store).
    // Otherwise, let it fall through to the code below

    dprintf ("handleStoreConditional: hit on %0llx\n", addr);    
   
    /* Check current state
       2.1 if it's M or E, we complete the store */
    if ((entry_flags->is_M() || entry_flags->is_E())) {
      //mark line as modified
      entry_flags->set_M();
      // clean up LLSC data structure
      llsc = NULL;
      StoreRequest* sr = CAST_OR_DIE(StoreRequest*, dr);
      dprintf ("handleStoreConditional: completing store on %0llx\n", addr);    
      return handleStoreHit(sr, entry);
    }// end case 2.1
    else { 
      /* 2.2 if it's not M or E, we need to upgrade */
      dprintf("handleStoreConditional: Need to force upgrade miss!\n");
      // we mark this as invalid and treat it as an upgrade miss
      entry_flags->setValid(false);
      hash->remove(entry);
      ASSERT(entry != NULL);
      delete entry;
      
      if (canMiss(addr)) {
	// miss, locate a new victim
	dprintf("handleStoreConditional: Could miss on addr=%0llx\n",addr);
	return handleMiss(dr, assignVictimLine(dr));
      }
      dprintf("handleStoreConditional: Could NOT miss on addr=%0llx\n",addr);
      //dprintf("handleStoreConditional: I have no idea what this means!\n");
      //ASSERT(0);
      return stall;
    }// end case 2.2
  }// end case 2
	  	  
   
    // store conditional has failed
    dprintf("handleStoreConditional: failed!\n");
    cacheLog->d("%s:  FAILURE\n", getName());
    Response* res = NEW Response(dr,1);
    res->setFlag(RESPONSE_NACK_ABORT);
    Result result = routePacket(res);            
    ASSERT(result == success);
    return success;
}

void MESICache::hookLoadRefill (LoadRequest * lr, MSHR* mshr) {
    MemoryRequest* ir = lr->initiatingRequest;

    VA addr = lr->getAddress();
    uint pid = (lr->getProcessor())->getID();
    ASSERT(pid+1);
    ASSERT(addr+1);
    dprintf("hookLoadRefill[%d]: %0llx\n", (int)pid, addr);

    CacheEntry* victimEntry = mshr->victimEntry;

    // for LRU replacement...
    victimEntry->touch();

    MESIFlags* victimEntry_flags = CAST_OR_DIE(MESIFlags*, victimEntry->flags);
    ASSERT(victimEntry_flags->isValid());

    // cs315a:
    // A refill has just happened, we need to update our flags.
    // ir is the request that caused the refill (a store or a load from the processor),
    // lr is the LoadRequest that was the refill, 
    // victimEntry is the CacheEntry that we just wrote to, and victimEntry_flags its MESIFlags.

    // i think we just need to set the flags as shared/exclusive/modified
    if (IS_A(LoadRequest*, ir)) {
      // shacham: check the shared flag of the request traversing the system, 
      //          not the original request from the cpu
      if (lr->isShared()){ 
	dprintf("hookLoadRefill[%d]: load returned, other caches had copy -> S\n", (int)pid);
	victimEntry_flags->set_S();
      }
      else {
	dprintf("hookLoadRefill[%d]: load returned, NO other caches had copy -> E\n", (int)pid);
	victimEntry_flags->set_E();
      }
    }
    else if (IS_A(StoreRequest*, ir)) {
      dprintf("hookLoadRefill[%d]: store returned -> M\n", (int)pid);
      victimEntry_flags->set_M();
    }
    else{
      dprintf("hookLoadRefill[%d]: ERROR: initiatingRequest is neither store nor load!\n", (int)pid);
      ASSERT(0);
    }
    ASSERT(ir); // appease unused variable warning
    //dprintf("hookLoadRefill: DONE\n");
}

void MESICache::hookPreRawFlush (RawFlushRequest* rfr) {
    // check first eviction buffer
    if (stalledEvict != NULL &&
        rfr->inBounds(stalledEvict->getAddress(), 
                      stalledEvict->getDataSize()))
    {
        cacheLog->d("%s:  RAWFLUSH FROM EVICT REQUEST %0llx ", 
                    getName(),
                    stalledEvict->getAddress());
        cacheLog->debugHex((uint*)stalledEvict->getData(),
                           blockSize/WORD_SIZE);

        ASSERT(stalledEvict->getDataSize() == blockSize);
        memcpy(ADDR_UINT32STAR(stalledEvict->getAddress()),
               stalledEvict->getData(), blockSize);
    }
}

void MESICache::hookVisitRawFlush (RawFlushRequest* rfr) {
    hash->visit(&Cache::visitRawFlush, rfr);
}

void MESICache::hookRawFlush (RawFlushRequest* rfr) {
    hookPreRawFlush(rfr);
    hookVisitRawFlush(rfr);

    // then request buffer
    for (List<PacketDelay*>::iterator iter = agingDataRequests->begin();
         iter != agingDataRequests->end(); iter++)
    {
        DataRequest* bdr = CAST_OR_DIE(DataRequest*, (*iter)->request);

        if (IS_A(StoreRequest*, bdr) &&
            rfr->inBounds(bdr->getAddress(), 
                          bdr->getDataSize()))
        {
            cacheLog->d("%s:  RAWFLUSH FROM REQUEST QUEUE %0llx ",
                        getName(),
                        bdr->getAddress());
            cacheLog->debugHex((uint*)bdr->getData(), blockSize/WORD_SIZE);

            ASSERT(bdr->getDataSize() == blockSize);
            memcpy(ADDR_UINT32STAR(bdr->getAddress()),
                   bdr->getData(), blockSize);
        }
    }
}


Result MESICache::handle_SnoopRequest (Packet* r) {
  //dprintf ("handle_SnoopRequest: START1\n");
    SnoopRequest* snr = CAST_OR_DIE(SnoopRequest*, r);
    //dprintf ("handle_SnoopRequest: START2\n");
    DataRequest* dr = CAST_OR_DIE(DataRequest*, snr->getPacket());


    List<MatchInfo*>* matches = snr->getMatches();
    findMatches(dr, matches, snr);
    if (matches->empty()) {
        cacheLog->d("%s:%lli:%0llx SNOOP MISS\n",
                    name,
                    global_sim->getSimclock(),
                    dr->getAddress());  
    } else {
        cacheLog->d("%s:%lli:%0llx SNOOP HIT (matches: %i)\n",
                    name,
                    global_sim->getSimclock(),
                    dr->getAddress(),
                    matches->size());
    }

    VA addr = blockAddr(dr->getAddress());
    dprintf("handle_SnoopRequest: %0llx from PID=%d\n", addr, (dr->getProcessor())->getID());


    // If this is not a LoadRequest (for example evict or something) than we don't care.
    if (! IS_A(LoadRequest*, dr)){
      ASSERT(matches != NULL);
      clearMatches(matches);
      delete matches;
      
      sendAck(NEW SnoopResponse(snr, dr->getDataSize(), NULL));
      stallBySnoop++;
      return success;
    }

    // cs315a: 
    // matches now contains a list of all the places in this cache
    // that have the line being snooped.  Your job is to handle the
    // snoop hit and populate the SnoopRequest snr appropriately.  To
    // put some data from your cache into the DataRequest inside the
    // SnoopRequest use updateSnoopRequest().
    //

    // find the matches in the caches,
    // for we must ensure: invalid lines it bashes.
    // :)
    ASSERT(matches->size() <= 1);
    ASSERT(IS_A(LoadRequest*, dr));
    LoadRequest * lr = CAST_OR_DIE(LoadRequest*,dr);
    /*
      We need to check ALL load requests with the link register 
      We need to invalidate any correspondong entries if it was exclusive
      We need to prevent any other cache from getting the line in E state if it was not exclusive request
    */
    bool llsc_matches=false;
    if (llsc == addr) {
      // There is an outstanding LL from this processor
      llsc_matches = true;
      // if the request is for exclusive state, remove the LL entry, and kill outstanding SC request
      if (lr->isExclusive()) {
	dprintf ("handle_SnoopRequest: load request to addr=%0llx is exclusive!\n", addr);
	dprintf ("handle_SnoopRequest: clearing link register addr=%0llx\n", addr);
	llsc = NULL;
	if (stalledLoad != NULL &&
	    isStoreConditional((DataRequest*)(stalledLoad->initiatingRequest)) &&
	    blockAddr(stalledLoad->initiatingRequest->getAddress())==addr){
	  dprintf("DEBUG: I have SC on the fly!! Cancel it... Quick... They're coming at you...\n");
	  // store conditional has failed
	  cacheLog->d("%s:  FAILURE\n", getName());
	  Response* res = NEW Response((DataRequest*)stalledLoad->initiatingRequest,1);
	  res->setFlag(RESPONSE_NACK_ABORT);
	  Result result = routePacket(res);            
	  ASSERT(result == success);
	  cancelRequests();
	}
      }
      else{
	// the request is for none exclusive. We are going to say that we have the line as shared
	// (even if we don't really have it) just to make sure that no one else get it in E state,
	// without us knowing about it.
	lr->setShared(true); 
      }
    }	
    
    
    List<MatchInfo*>::iterator i;
    for (i = matches->begin(); i != matches->end(); i++) { //f

      MatchInfo * match = *i;
      dprintf ("handle_SnoopRequest: match in %s\n", match->getFlagName());

      switch(match->getFlag()) {
      case MATCH_INPUT_BUFFER:
	handle_SnoopRequest_ib(snr, match);
	break;
    
      case MATCH_CACHE_HASH:
	handle_SnoopRequest_ch(snr, match);	
	break;
	
      case MATCH_MSHR:
	handle_SnoopRequest_mshr(snr, match);		
	break;
	
      case MATCH_OUTPUT_BUFFER:
	handle_SnoopRequest_ob(snr, match);		
	break;
	
      case MATCH_OVERFLOW_MANAGER:
	handle_SnoopRequest_om(snr, match);		
	break;
	
      default:
	printf("Error in MESICache::removeEntry(): No matching case\n");
	ASSERT(0);
	break;
      }   
    } //end for loop


    ASSERT(matches != NULL);
    clearMatches(matches);
    delete matches;
    sendAck(NEW SnoopResponse(snr, dr->getDataSize(), NULL));
    stallBySnoop++;
    return success;
}


void MESICache::handle_SnoopRequest_ib(SnoopRequest * snr, MatchInfo * match) {
  // no L2, so do nothing
}

void MESICache::handle_SnoopRequest_ch(SnoopRequest * snr, MatchInfo * match) {
  DataRequest* dr = CAST_OR_DIE(DataRequest*, snr->getPacket());
  LoadRequest * lr = CAST_OR_DIE(LoadRequest*,dr);

  // get our flags
  CacheEntry* entry =  match->getMatchingEntry();
  MESIFlags* ourflag = CAST_OR_DIE(MESIFlags*, entry->flags);
    
  // if it's requesting line as exclusive
  if (lr->isExclusive()) {
    dprintf ("handle_SnoopRequest: load request is exclusive!\n");
    if (ourflag->is_S()) {
      dprintf ("handle_SnoopRequest: we are shared .. downgrade to I!\n");
      ourflag->set_I();
      ASSERT(match != NULL);
      ASSERT(entry != NULL);
      removeEntry(match);
      delete entry;
    }
    else if (ourflag->is_E()) {
      dprintf ("handle_SnoopRequest: we are exclusive .. downgrade to I!\n");
      ourflag->set_I();
      ASSERT(match != NULL);
      ASSERT(entry != NULL);
      removeEntry(match);
      delete entry;
    }
    else if (ourflag->is_M()) {
      dprintf ("handle_SnoopRequest: we are modified .. downgrade to I, send data!\n");
      ourflag->set_I();
      updateSnoopRequest (snr, 
			  (match->getMatchingEntry())->data,
			  (match->getMatchingEntry())->getBlockSize(),
			  true);
      ASSERT(match != NULL);
      ASSERT(entry != NULL);
      removeEntry(match);
      delete entry;
    }
    else {
      dprintf("handle_SnoopRequest: strange condition! (our data match but is in invalid state)\n");
      ASSERT(0);
    }
  }
  // it's requesting NOT exclusive
  else {
    dprintf ("handle_SnoopRequest: load request is NOT exclusive!\n");
    lr->setShared(true); // since we found the line in this cache
    if (ourflag->is_S()) {
      dprintf ("handle_SnoopRequest: we are shared .. do nothing!\n");
    }
    else if (ourflag->is_E()) {
      dprintf ("handle_SnoopRequest: we are exclusive .. downgrade to S!\n");
      ourflag->set_S();
    }
    else if (ourflag->is_M()) {
	Ocounter = Ocounter + 1;
      dprintf ("handle_SnoopRequest: we are modified .. downgrade to S, send data!\n");
      ourflag->set_S();
      updateSnoopRequest (snr, 
			  (match->getMatchingEntry())->data,
			  (match->getMatchingEntry())->getBlockSize(),
			  true);
    }
    else {
      dprintf("handle_SnoopRequest: strange condition! (our data match but is in invalid state)\n");
      ASSERT(0);   
    }
  }
}


void MESICache::handle_SnoopRequest_mshr(SnoopRequest * snr, MatchInfo * match) {
  // treat like shared cache line
  //  DataRequest* dr = CAST_OR_DIE(DataRequest*, snr->getPacket());
  //LoadRequest * lr = CAST_OR_DIE(LoadRequest*,dr);
  ASSERT(0);
}


void MESICache::handle_SnoopRequest_ob(SnoopRequest * snr, MatchInfo * match) {

  // this is an eviction request --> treat like dirty cache line
  DataRequest* dr = CAST_OR_DIE(DataRequest*, snr->getPacket());
  LoadRequest * lr = CAST_OR_DIE(LoadRequest*,dr);
  ASSERT(lr);
  ASSERT(stalledEvict != NULL);

  // stalledEvict->getData() returns a const char* so we first copy it
  // in order to use it.
  char evictedData[stalledEvict->getDataSize()];
  ASSERT(stalledEvict->getData());
  memcpy(evictedData, stalledEvict->getData(), stalledEvict->getDataSize());

  updateSnoopRequest (snr,
		      evictedData,
		      stalledEvict->getDataSize(),
		      true);

  // cancel eviction
  delete stalledEvict;
  stalledEvict = NULL;
}


void MESICache::handle_SnoopRequest_om(SnoopRequest * snr, MatchInfo * match) {
  // not sure what overflow manager is
  ASSERT(0);

}


void MESICache::sendAck (SnoopResponse* snrs) {
    SnoopRequest* snr = CAST_OR_DIE(SnoopRequest*, snrs->getRequest());
    DataRequest* dr = CAST_OR_DIE(DataRequest*, snr->getPacket());

     if (IS_A(LoadRequest*, dr) && snr->hasData()) {
        cacheLog->d("%s:%lli:%0llx SENDING ACK WITH RESPONSE\n",
                    name,
                    global_sim->getSimclock(),
                    dr->getAddress());

        // we create another SnoopACK since the two objects will be deleted
        // separately
        if (routePacket(NEW SnoopACK(snr, RESPONSE_SNOOP_RESPONSE, 1)) == stall) {
            ASSERT(false);
        }
        
        SnoopACK* ack = NEW SnoopACK(snr, RESPONSE_SNOOP_RESPONSE, 1);
        snrs->setSnoopACK(ack);
        if (routePacket(snrs) == stall) {
            stalledSnoopResponse->add(snrs);
        }
  
    } else {    
        cacheLog->d("%s:%lli:%0llx SENDING ACK WITHOUT RESPONSE\n",
                    name,
                    global_sim->getSimclock(),
                    dr->getAddress());

        if (routePacket(NEW SnoopACK(snr, RESPONSE_NO_SNOOP_RESPONSE, 1)) == stall) {
            ASSERT(false);
        }

        // we send only a snoop ack, not a snoop response.
	ASSERT(snrs != NULL);
        delete snrs;
    }
}

void MESICache::clearMatches (List<MatchInfo*>* matches) {
    List<MatchInfo*>::iterator i;
    for (i = matches->begin(); i != matches->end(); i++) {
	ASSERT(*i != NULL);
        delete *i;
    }
    matches->clear();
}

void MESICache::removeEntry (MatchInfo * match) {
  CacheEntry* entry =  match->getMatchingEntry();

  switch(match->getFlag()) {
  case MATCH_INPUT_BUFFER:
    dprintf("removeEntry: MATCH_INPUT_BUFFER\n");   
    break;
    
  case MATCH_CACHE_HASH:
    dprintf("removeEntry: MATCH_CACHE_HASH\n"); 
    hash->remove(entry);
    break;
    
  case MATCH_MSHR:
    dprintf("removeEntry: MATCH_MSHR\n");  
    
    break;

  case MATCH_OUTPUT_BUFFER:
    dprintf("removeEntry: MATCH_OUTPUT_BUFFER\n");    
    break;
    
  case MATCH_OVERFLOW_MANAGER:
    dprintf("removeEntry: MATCH_OVERFLOW_MANAGER\n");    
    break;
    
  default:
    dprintf("Error in MESICache::removeEntry(): No matching case\n");
    ASSERT(0);
    break;

  }


}

Result MESICache::handle_RawMemoryRequest (Packet* r) {
    RawMemoryRequest* rmr = CAST_OR_DIE(RawMemoryRequest*, r);
    List<MatchInfo*>* matches = NEW List<MatchInfo*>();

    // fake a datarequest...
    DataRequest* dr = NEW DataRequest(rmr->getAddress(),
                                      CAST_OR_DIE(Processor*, r->getSource()),
                                      this,
                                      this,
                                      0,
                                      0);
    findMatches(dr, matches, rmr);
    ASSERT(dr != NULL);
    delete dr;

    if (!matches->empty()) {
        memcpy(rmr->getData(),
               (*(matches->begin()))->getMatchingEntry()->data,
               global_sim->cfg.blocksize);
	ASSERT(matches != NULL);
        clearMatches(matches);
        delete matches;
    } else {
	ASSERT(matches != NULL);
        clearMatches(matches);
        delete matches;
        return Cache::handle_RawMemoryRequest(r);
    }

    return success;
}

void MESICache::findMatches (DataRequest* dr,
                             List<MatchInfo*>* matches,
                             Request* r)
{
    // 1. check the output buffer
    if (findMatchInOutputBuffer(dr->getAddress(), matches, dr, r)) {
        cacheLog->d("%s:%lli:%0llx %s FOUND MATCH IN OUTPUT BUFFER:\n",
                    dr->getSource()->getName(),
                    global_sim->getSimclock(),
                    dr->getAddress(),
                    name);    
    }
    
    // 2. check the victim entry
    if (findMatchInMSHR(dr->getAddress(), matches, dr, r)) {
        cacheLog->d("%s:%lli:%0llx %s FOUND MATCH IN MSHR:\n",
                    dr->getSource()->getName(),
                    global_sim->getSimclock(),
                    dr->getAddress(),
                    name);  
    }

    // 3. check the cache
    if (findMatchInCacheHash(dr->getAddress(), matches, dr, r)) {
        cacheLog->d("%s:%lli:%0llx %s FOUND MATCH IN CACHE HASH:\n",
                    dr->getSource()->getName(),
                    global_sim->getSimclock(),
                    dr->getAddress(),
                    name);      
    }

    // 4. check the input buffer
    // even though we hit the cache there might be fresher data
    // in our input data
    if (findMatchInInputBuffers(dr->getAddress(), matches, dr, r)) {
        cacheLog->d("%s:%lli:%0llx %s FOUND MATCH IN INPUT BUFFER:\n",
                    dr->getSource()->getName(),
                    global_sim->getSimclock(),
                    dr->getAddress(),
                    name);      
    }    
}

bool MESICache::findMatchInOutputBuffer (VA addr,
                                         List<MatchInfo*>* matches,
                                         DataRequest* dr,
                                         Request* r)
{
    if (stalledEvict != NULL && stalledEvict->getAddress() == addr) {   
        matches->push_back(NEW MatchInfo(MATCH_OUTPUT_BUFFER,
                                         r,
                                         this,
                                         stalledEvict));
        return true;
    }
    return false;
}

bool MESICache::findMatchInMSHR (VA addr,
                                 List<MatchInfo*>* matches,
                                 DataRequest* dr,
                                 Request* r)
{
    bool found = false;
    // check for a conflict with victimEntries
    for (List<MSHR*>::iterator iter = mshrs->begin();
         iter != mshrs->end();
         ++iter)
    {
        MSHR* const mshr = *iter;
        if (!(mshr->victimEntry->flags->isValid() &&
              mshr->victimEntry->getAddress() == addr)) {
            continue;
        }

        // victimEntry should be in S
        ASSERT(((MESIFlags*)mshr->victimEntry->flags)->isShared() && 
               !mshr->victimEntry->flags->isDirty());
        ASSERT(mshr->victimEntry->location == NULL);

        matches->push_back(NEW MatchInfo(MATCH_MSHR,
                                         r,
                                         this,
                                         dr,
                                         mshr->victimEntry));
        found = true;
    }

    return found;
}


bool MESICache::findMatchInCacheHash (VA addr,
                                      List<MatchInfo*>* matches,
                                      DataRequest* dr,
                                      Request* r)
{
    List<CacheEntry*>* list = hash->retrieve(addr);
    bool found = false;
    if (!list->empty()) {
        for (List<CacheEntry*>::iterator iter = list->begin();
             iter != list->end();
             ++iter)
        {
            CacheEntry* const cEntry = *iter;
            MESIFlags* const cEntry_flags =
                CAST_OR_DIE(MESIFlags*, cEntry->flags);
            ASSERT(cEntry_flags->isValid());

            matches->push_back(NEW MatchInfo(MATCH_CACHE_HASH,
                                             r,
                                             this,
                                             dr,
                                             cEntry));
        }
        found = true;
    }
    ASSERT(list != NULL);
    delete list;
    return found;
}

bool MESICache::findMatchInInputBuffers (VA addr,
                                         List<MatchInfo*>* matches,
                                         DataRequest* dr,
                                         Request* r)
{
    bool found = false;

    // dataRequests is an input delay queue, so it should never
    // have anything in it for L1
    ASSERT(!(delay == 0 && !agingDataRequests->empty()));

    // check out the special case where the request buffer to see
    // if there is a race between an eviction or flush from L1 and
    // this snoop message.

    for (List<PacketDelay*>::iterator iter = agingDataRequests->begin();
         iter != agingDataRequests->end(); 
         iter++)
    {
        DataRequest* bdr = CAST_OR_DIE(DataRequest*, (*iter)->request);
        if (bdr->getAddress() == addr) {
            matches->push_back(NEW MatchInfo(MATCH_INPUT_BUFFER,
                                             r,
                                             this,
                                             bdr));
            found = true;
        }
    }

    return found;
}

void MESICache::updateSnoopRequest (SnoopRequest* snr,
                                    void* data,
                                    uint dataSize,
                                    bool dirty)
{
    LoadRequest* lr = CAST_OR_DIE(LoadRequest*, snr->getPacket());

    if (!snr->hasData()) {
        lr->insertData(data, dataSize);
        snr->setHasData(true); 
    }

    if (dirty && !snr->hasDirtyLine()) {
        snr->setDirtyLine(true);
        lr->aborted = true;
    }
}

void MESICache::cancelIncomingRequest (DataRequest* bdr) {
    for (List<PacketDelay*>::iterator iter = agingDataRequests->begin();
         iter != agingDataRequests->end(); 
         iter++) {
        PacketDelay* _pd = *iter;
        DataRequest* _bdr = CAST_OR_DIE(DataRequest*, _pd->request);
        if (_bdr == bdr) {
            agingDataRequests->remove(_pd);
            break;
        }
    } 

    ASSERT(bdr != NULL);
    delete bdr;
}

Result MESICache::handle_SnoopDelay (Packet* r) {
    if (snoopDelay == 0) {
        // this is for non-last private caches and if all the caches
        // have 0 snoopDelay
        handle_SnoopRequest(r);
    } else {
        // if the delay(hittime) is non-zero, defer its delivery.
        PacketDelay* rd = (PacketDelay*)CALLOC(1, sizeof(PacketDelay));
        rd->request = (SnoopRequest*)r;
        rd->alarm = global_sim->getSimclock() + snoopDelay;

        snoopRequests->add(rd);
    }

    return success;
}

bool MESICache::isTherePendingSnoopRequest(VA addr) {
    // in the current system designs, a shared cache never snoops.
    if (isSharedCache()) {
        ASSERT(snoopRequests->empty());
    }
    
    for (List<PacketDelay*>::iterator iter = snoopRequests->begin();
         iter != snoopRequests->end(); ++iter)
    {
        PacketDelay* pd = snoopRequests->remove();
        SnoopRequest* r = CAST_OR_DIE(SnoopRequest*, pd->request);    
        DataRequest* dr = CAST_OR_DIE(DataRequest*, r->getPacket());
        if (blockMatch(dr->getAddress(), addr)) {
            return true;
        }
    }

    return false;
}

bool MESICache::isL1 () {
    return (strstr(name, "L1") != NULL);
}

LoadRequest* MESICache::makeRefillRequest (DataRequest* dr) {
  //dprintf("makeRefillRequest: START\n");
    LoadRequest* lr = Cache::makeRefillRequest(dr);

    VA addr = dr->getAddress();
    uint pid = (dr->getProcessor())->getID();
    ASSERT(pid+1);
    ASSERT(addr+1);
    dprintf("makeRefillRequest[%d]: %0llx\n", (int)pid, addr);


    // cs315a: 
    // this is called by the cache to make the LoadRequest that is
    // sent out to perform a refill.  Cache::makeRefillRequest just
    // creates a normal LoadRequest for the correct data, in some
    // cases you'll need to change the flags of the LoadRequest

    // check if dr is a shared or exclusive request and modify the load request (lr) feilds
    // 
    if (IS_A(StoreRequest*, dr)){
      dprintf("makeRefillRequest: dr is store request\n");
      lr->setExclusive(true);
    }
    else if (IS_A(LoadRequest*, dr)){
      dprintf("makeRefillRequest: dr is load request\n");
      lr->setExclusive(false);
    }
    dprintf("makeRefillRequest: DONE\n");
    return lr;
}
